//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using OpenLS.Core;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.Parsing;
using Expression = OpenLS.Spreadsheet.Expressions.Expression;

namespace OpenLS.Spreadsheet
{
    ///<summary>
    /// Represents a cell in a worksheet. 
    ///</summary>
    public partial class Cell : INotifyPropertyChanged, ICellEnumerator
    {
#if DEBUG
        internal bool IsDeleted
        {
            get { return Worksheet[Address] != this; }
        }

#endif

        private Cell(Row row)
        {
            _row = row;
        }


        internal bool IsVisible
        {
            get
            {
                if (RowSpan == 1 && ColumnSpan == 1)
                    return Row.IsVisible && Column.IsVisible;
                return Enumerable.Any(GetRows(), r => r.IsVisible) && Enumerable.Any(GetColumns(), c => c.IsVisible);
            }
        }

        /// <summary>
        /// Gets or sets an arbitrary object value that can be used to store custom information about this cell.
        /// </summary>
        public object Tag
        {
            get
            {
                if (!HasExtraData)
                    return null;
                return ExtraData.Tag;
            }
            set
            {
                if (value == Tag)
                    return;
                if (value == null)
                {
                    if (!HasExtraData)
                        return;
                    Contract.Assert(HasExtraData);
                    ExtraData.Tag = null;
                    ForgetExtraData();
                }
                else
                {
                    EnsureExtraData.Tag = value;
                }
                notifyNonRecordPropertyChanged("Tag");
            }
        }


        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override string ToString()
        {
            return Label;
        }

        internal Cell(Row row, Column column)
            : this(row)
        {
            Contract.Requires(row != null);
            Contract.Requires(column != null);
            _column = column;
        }


        private Cell(Row row, Column column, object value)
            : this(row, column)
        {
            Value = value;
        }

        public static Cell operator +(Cell cell, CellRelativeAddress offset)
        {
            Contract.Requires((cell != null));
            if (offset.IsZero)
                return cell;
            return cell.Worksheet[cell.Row.Index + offset.Row, cell.Column.Index + offset.Column];
        }

        public bool CanOverflow
        {
            get
            {
                if (!(Value is string))
                    return false;
                return TextWrapping == TextWrapping.NoWrap;
            }
        }

        internal double DesiredHeight
        {
            get
            {
                if (_value == null || IsVolatile || FormattingRecord == null)
                    return Worksheet.DefaultRowHeight;
                if (TextWrapping == TextWrapping.NoWrap)
                    return Worksheet.FontMetrics.GetHeightForFormattingRecord(FormattingRecord, Workbook);
                var info = new CellTextRenderingInformation(new CellDisplayInfoKey(this, ColorMode.Color));
                return info.TextHeight;
            }
        }

        public double DesiredWidth
        {
            get
            {
                if (_value == null || IsVolatile)
                    return 0;
                object o = getFormatedValue(Format, Value, (int) Column.ActualCharacterWidth);
                //\\ CellNumberFormatConverter.GetFormattedValue(Value, this.Format, (int)this.Column.ActualCharacterWidth);
                Typeface tf ;//\\= new Typeface(FontFamily, FontStyle, FontWeight, FontStretches.Normal);
                tf = SpreadsheetFont.CreateTypeface(FontFamily, FontStyle, FontWeight);
                int offset = 2;
                if (!IsVolatile && Table != null && Table.IsHeader(this))
                    offset += 18;
                return GetObjectWidth(o, tf) + offset;
            }
        }

        private double GetObjectWidth(object o, Typeface tf)
        {
            if (o is string)
                return GetStringWidth((string) o, tf);
            if (o is CompositeFormattedValue)
            {
                double result = 0;
                foreach (object o2 in ((CompositeFormattedValue) o).Items)
                    result += GetObjectWidth(o2, tf);
                return result;
            }
            if (o is InvisibleText)
                return GetObjectWidth(((InvisibleText) o).Text, tf);
            if (o is RepeatText)
                return 0;
            throw new NotSupportedException();
        }

        private double GetStringWidth(string s, Typeface tf)
        {
            var t = new FormattedText(s,
                                      CultureInfo.CurrentCulture, FlowDirection.LeftToRight, tf,
                                      PointsToPixelConverter.ToPixel(FontSize), Brushes.Black)
                        {Trimming = TextTrimming.None};
            return t.WidthIncludingTrailingWhitespace;
        }

        internal static Cell CreateVolatileCell(Row row, Column column)
        {
            var result = new Cell(row, column);
            result.setRawValue(new VolativeValue(result));
            if (row.FormattingRecord != null)
                result.FormattingRecord = row.FormattingRecord;
            if (column.FormattingRecord != null)
                result.FormattingRecord = column.FormattingRecord;
            return result;
        }

        internal void setRawValue(object o)
        {
            Contract.Assert(!(o is RichTextParagraph));
            _value = o;
        }

        private static readonly object notCalculatedValue = new UncalculatedValue();

        private bool NeedsCalculate
        {
            get { return _value == notCalculatedValue; }
        }

        /*internal bool IsVolatile
        {
            get { return _value is VolativeValue; }
        }*/
        internal CellState State
        {
            get
            {if (_value is VolativeValue)
            return CellState.Volatile;
            return CellState.Promoted;}
        }
        internal bool IsVolatile
        {
            get { return this.State == CellState.Volatile; }
        }
        internal enum CellState: byte
        {
            Volatile,
            Promoted,
        }

        internal void InvalidateExpression()
        {
            EnsurePromoted();
            if (Expression == null)
                return;
            setRawValue(notCalculatedValue);
        }

        private void EnsurePromoted()
        {
            Contract.Ensures(this.State == CellState.Promoted);
            if (State == CellState.Volatile)
                promote();
        }

        /// <summary>
        /// Get or get the style of the row or column
        /// </summary>
        public CellStyle Style
        {
            get
            {
                if (FormattingRecord == null)
                    return null;
                if (FormattingRecord.Parent == null)
                    return null;
                return FormattingRecord.Parent.Style;
            }
            set
            {
                Contract.Ensures(Style == value);
                using (Workbook.CreateUndoScope(StringTable.Style))
                {
                    if (value == null)
                    {
                        if (FormattingRecord == null)
                            return;
                        CellFormattingRecord r = FormattingRecord.Clone();
                        r.SetParent(null);
                        SetFormattingRecordSafe(r);
                        //FormattingRecord = Workbook.StyleSheet.LookupNewCellFormattingRecord(r);

                    }
                    else
                    {
                        CellFormattingRecord r = Spreadsheet.FormattingRecord.CreateFromParent(value.Record);
                        if (value.Format == null && Format != null)
                            r.Format = Format;
                        Contract.Assert(r.Parent == value.Record);
                        SetFormattingRecordUnsafe(Workbook.StyleSheet.LookupNewCellFormattingRecord(r));
                        Contract.Assert(FormattingRecord.Parent == value.Record);
                        Contract.Assert(FormattingRecord.Parent.Style == value);
                        Contract.Assert(Style == value);
                    }
                }
                Worksheet.OnCellBorderOrBackgroundChanged(this);

            }
        }

        ///<summary>
        /// Set the style of the cell. This method extracts the properties of the style and applies them to the cell.
        ///</summary>
        ///<param name="style">The style to apply</param>
        ///<exception cref="ArgumentNullException"></exception>
        [Obsolete("Use the Style property instead")]
        public void SetStyle(CellStyle style)
        {
            if (style == null)
                throw new ArgumentNullException("style");
            using (Workbook.CreateUndoScope(StringTable.Style))
            {
                CellFormattingRecord r = Spreadsheet.FormattingRecord.CreateFromParent(style.Record);
                Contract.Assert(r.Parent == style.Record);
                if (style.Format == null && Format != null)
                    r.Format = Format;
                Contract.Assert(r.Parent == style.Record);
                SetFormattingRecordUnsafe(Workbook.StyleSheet.LookupNewCellFormattingRecord(r));
                Contract.Assert(FormattingRecord.Parent == style.Record);
            }
        }

        public CellAddress Address
        {
            get { return new CellAddress(Row.Index, Column.Index); }
        }

        internal CellAddress AddressUnsafe
        {
            get { return new CellAddress(Row.Index, Column.Index); }
        }

#if DEBUG
        [ContractInvariantMethod]
        // ReSharper disable UnusedMember.Global
        private void ObjectInvariant()
            // ReSharper restore UnusedMember.Global
        {
            //    Contract.Invariant(Column != null);
            Contract.Invariant(Row != null);
            //            Contract.Invariant(IsVolatile || Column[Row.Index] == this, "valid column for " + Label);
            //          Contract.Invariant(IsVolatile || Row[Column.Index] == this, "valid row for " + Label);
            //        if (Table != null)
            {
                //          Contract.Invariant(Table.Range.Contains(this), string.Format("{0} not in {1}", Label, Table.Range.Label));
            }
        }
#endif


        public TextWrapping TextWrapping
        {
            get
            {
                if (FormattingRecord == null)
                    return TextWrapping.NoWrap;

                return FormattingRecord.TextWrapping;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.TextWrapping = value;
                SetFormattingRecord(r, CellProperty.TextWrapping, "Text wrapping"); //\\loc
                notifyNonRecordPropertyChanged(CellProperties.CanOverflow);
                notifyNonRecordPropertyChanged(CellProperties.VerticalAlignment);
                notifyNonRecordPropertyChanged(FormattedValueName);
            }
        }

        private void SetFormattingRecord(CellFormattingRecord r, CellProperty changedProperty, string unitName)
        {
            using (Workbook.CreateUndoScope(unitName))
            {
                SetFormattingRecord(r, changedProperty);
            }
        }

        private void SetFormattingRecord(CellFormattingRecord r, CellProperty changedProperty)
        {
            CellFormattingRecord oldR = FormattingRecord;
            FormattingRecord = r != null ? Workbook.StyleSheet.LookupNewCellFormattingRecord(r) : null;
            if (UndoEnabled)
            {
                Workbook.AddUndoUnit(new CellFormattingRecordUndoUnit(this, oldR));
            }
            //if (UndoEnabled)
            //  Workbook.ActionManager.AddAction(new CellStyleRecordAction(changedProperty.ToString(), this, oldR, StyleRecord));
            setDirty();
            EnsurePromoted();
            notifyRecordPropertyChanged(changedProperty);
            notifyFormattedValueChanged();
        }

        private const string FormattedValueName = "FormattedValue";

        private bool UndoEnabled
        {
            get { return Workbook.UndoEnabled; }
        }


        internal Cell GetNeighbor(NavigationDirection k)
        {
            Contract.Ensures(Contract.Result<Cell>() != this);
            CellAddress old = Address;
            while (true)
            {
                CellAddress moved = old.GetNeighbor(k);
                Cell result = Worksheet[moved];
                bool isConsideredVisible = result.IsVisible;
                if (!isConsideredVisible)
                {
                    switch (k)
                    {
                        case NavigationDirection.Down:
                        case NavigationDirection.Up:
                            isConsideredVisible = result.Row.IsVisible;
                            break;
                        case NavigationDirection.Left:
                        case NavigationDirection.Right:
                            isConsideredVisible = result.Column.IsVisible;
                            break;
                    }
                }
                if (isConsideredVisible )
                {
                    if (result != this)
                        return result;
                }
                if (moved.Equals(old))
                {
                    if (result == this)
                        return null;
                    return result;
                }
                old = moved;
            }
        }

        internal bool HasSpan
        {
            get { return RowSpan != 1 || ColumnSpan != 1; }
        }

        internal CellAddress GetBottomRight()
        {
            return Address + new CellRelativeAddress(RowSpan - 1, ColumnSpan - 1);
        }



        ///<summary>
        /// Get the row span of the cell.
        ///</summary>
        public int RowSpan
        {
            get
            {
                if (!HasExtraData)
                    return 1;
                return ExtraData.RowSpan;
            }
            internal set
            {
                Contract.Requires(State == CellState.Promoted);
                if (value == RowSpan)
                    return;
                EnsureExtraData.RowSpan = value;
                ForgetExtraData();
                notifyNonRecordPropertyChanged(CellProperties.RowSpan);
            }
        }

        ///<summary>
        /// Get the row span of the cell.
        ///</summary>
        public int ColumnSpan
        {
            get
            {
                if (!HasExtraData)
                    return 1;
                return ExtraData.ColumnSpan;
            }
            internal set
            {
                Contract.Requires(State == CellState.Promoted);
                if (value == ColumnSpan)
                    return;
                EnsureExtraData.ColumnSpan = value;
                ForgetExtraData();
                notifyNonRecordPropertyChanged(CellProperties.ColumnSpan);
            }
        }


        public double Indentation
        {
            get
            {
                if (FormattingRecord == null)
                    return 0;
                return FormattingRecord.Indentation;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.Indentation = value;
                SetFormattingRecord(r, CellProperty.Indentation, "Indentation");
            }
        }


        public bool IsVertical
        {
            get
            {
                if (FormattingRecord == null)
                    return false;
                return FormattingRecord.TextRotation == Spreadsheet.FormattingRecord.VerticalRotation;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.TextRotation = value ? Spreadsheet.FormattingRecord.VerticalRotation : 0;
                SetFormattingRecord(r, CellProperty.IsVertical, "Vertical"); //\\
            }
        }

        public FontStyle FontStyle
        {
            get
            {
                if (FormattingRecord == null)
                    return FontStyles.Normal;
                return FormattingRecord.FontStyle;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.FontStyle = value;
                SetFormattingRecord(r, CellProperty.FontStyle, "Font style");
            }
        }

        public FontStyle ActualFontStyle
        {
            get
            {
                DifferentialStyleRecord r = GetDifferentialStyleFor(rr => rr.Font != null);
                if (r != null)
                    return r.Font.FontStyle;

                return FontStyle;
            }
        }

        private DifferentialStyleRecord GetDifferentialStyleFor(Predicate<DifferentialStyleRecord> pred)
        {
            DifferentialStyleRecord r = DifferentialStyleRecord;
            if (r != null && pred(r))
                return r;

            if (Table != null && hasDefaultFormattingRecord())
            {
                r = Table.GetDifferentialStyleFor(this, pred);
                if (r != null && r.Font != null)
                    return r;
            }
            return null;
        }

        public UnderlineType ActualFontUnderline
        {
            get
            {
                DifferentialStyleRecord r = GetDifferentialStyleFor(rr => rr.Font != null);
                if (r != null)
                    return r.Font.Underline;
                return FontUnderline;
            }
        }

        public UnderlineType FontUnderline
        {
            get
            {
                if (FormattingRecord == null)
                    return UnderlineType.None;
                return FormattingRecord.FontUnderline;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();

                r.FontUnderline = value;
                SetFormattingRecord(r, CellProperty.FontUnderline, "Font underline");
            }
        }

        ///<summary>
        /// Get the actual font weight of the cell.
        ///</summary>
        public FontWeight ActualFontWeight
        {
            get
            {
                DifferentialStyleRecord r = GetDifferentialStyleFor(rr => rr.Font != null);
                if (r != null)
                    return r.Font.FontWeight;


                return FontWeight;
            }
        }

        ///<summary>
        /// Get or set the font weight of the cell.
        ///</summary>
        public FontWeight FontWeight
        {
            get
            {
                if (FormattingRecord == null)
                    return FontWeights.Normal;
                if (FormattingRecord.Font == null)
                    return FontWeights.Normal;
                return FormattingRecord.Font.FontWeight;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.FontWeight = value;
                SetFormattingRecord(r, CellProperty.FontWeight, StringTable.FontWeight);
            }
        }

        internal bool CanBeEdited
        {
            get
            {
                if (IsLocked && Worksheet.IsProtected())
                    return false;
                return true;
            }
        }

        ///<summary>
        /// Set or get whether the cell is locked and cannot be edited. This value of this property is meaningful only if the worksheet is protected.
        ///</summary>
        public bool IsLocked
        {
            get
            {
                if (FormattingRecord == null)
                    return Spreadsheet.FormattingRecord.IsLockedDefault;
                return FormattingRecord.IsLocked;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.IsLocked = value;
                SetFormattingRecord(r, CellProperty.IsLocked, "Locked");
            }
        }

        ///<summary>
        /// Set or get whether the cell's formula is hidden in the formula bar. This value of this property is meaningful only if the worksheet is protected.
        ///</summary>
        public bool IsFormulaHidden
        {
            get
            {
                if (FormattingRecord == null)
                    return false;
                return FormattingRecord.IsHidden;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.IsHidden = value;
                SetFormattingRecord(r, CellProperty.IsHidden, "Hidden");
            }
        }


        ///<summary>
        /// Get the font family of the cell.
        ///</summary>
        public FontFamily FontFamily
        {
            get
            {
                if (FormattingRecord == null || FormattingRecord.Font == null ||
                    FormattingRecord.Font.FontFamily == null)
                    return Workbook.FontFamily;
                return FormattingRecord.Font.FontFamily;
            }
            set
            {
                if (Equals(FontFamily, value))
                    return;
                CellFormattingRecord r = newFormattingRecord();
                r.FontFamily = value;
                SetFormattingRecord(r, CellProperty.FontFamily, "Font family");
                Row.InvalidateHeight();
            }
        }

        internal double ActualFontSizeInPixel
        {
            get { return ActualFontSize*4.0/3.0; }
        }

        public double ActualFontSize
        {
            get
            {
                DifferentialStyleRecord r = GetDifferentialStyleFor(rr => rr.Font != null);
                if (r != null)
                    return r.Font.Size;
                return FontSize;
            }
        }

        ///<summary>
        /// Get or set the font size of the cell.
        ///</summary>
        public double FontSize
        {
            get
            {
                if (FormattingRecord == null || FormattingRecord.Font == null)
                    return 11d;
                return FormattingRecord.Font.Size;
            }
            set
            {
                if (FontSize == value)
                    return;
                CellFormattingRecord r = newFormattingRecord();
                r.FontSize = value;
                SetFormattingRecord(r, CellProperty.FontSize, "Font size"); //\\loc
                notifyNonRecordPropertyChanged(CellProperties.ActualFontSize);
                Row.InvalidateHeight();
            }
        }


        ///<summary>
        /// Get or set the comment of the cell.
        ///</summary>
        public Comment Comment
        {
            get
            {
                if (!HasExtraData || ExtraData.Comment == null)
                    return null;
                //\\Comment = new Comment(this, null, null);
                return ExtraData.Comment;
            }
            set
            {
                using (this.Workbook.CreateUndoScope(StringTable.Comment))
                {
                    this.Workbook.AddUndoAction(this.Comment, c => this.Comment = c);
                    EnsureExtraData.Comment = value;
                    if (value != null)
                        value.SetCell(this);
                    ForgetExtraData();
                    notifyNonRecordPropertyChanged(CellProperties.Comment);
                    promote();

                    Worksheet.IncrementGeneration();
                }
            }
        }

        private object getFormatedValue(Format format, object value, int maxCount) //\\move somewhere else
        {
            if (value == null)
                return "";
            if (value is Error)
                return ((Error) value).Code;
            if (!format.CanFormat(value))
            {
                if (value is decimal && format.CanFormat(0d))
                    value = (double) (decimal) value;
                else if (value is DateTime)

                    value = Workbook.ChangeType<double>((DateTime) value);
                else if (value is double)
                {
                    var d = (double) value;
                    if (!DateTimeConverter.IsValidDouble(d))
                        return "####################################";
                    value = Workbook.ChangeType<DateTime>(d);
                }
            }
            if (value is RichTextParagraph)
                return ((RichTextParagraph)value).Text;
            if (format is GeneralFormat)
            {
                return ((GeneralFormat) format).GetFormattedValue(value, maxCount);
            }
            return format.GetFormattedValue(value);
        }

        private static Color? getForeground(Format format, object value)
        {
            Color? color = format.GetColor(value);
            return color;
        }


        ///<summary>
        /// Get or set the foreground color of the cell.
        ///</summary>
        public SpreadsheetColor Foreground
        {
            get
            {
                if (FormattingRecord == null)
                    return Colors.Black;
                if (FormattingRecord.Format != null)
                {
                    Color? b = getForeground(FormattingRecord.Format, Value);
                    if (b != null)
                        return b.Value;
                }
                return FormattingRecord.Foreground;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                if (r.Font == null)
                    r.Font = new Font();
                if (value == null)
                {
                    r.Foreground = value;
                }
                else
                {
                    r.Font = r.Font.Clone();
                    r.Foreground = value;
                }
                SetFormattingRecord(r, CellProperty.Foreground, "Foreground");
                notifyFormattedValueChanged();
            }
        }

        private CellFormattingRecord newFormattingRecord()
        {
            CellFormattingRecord result = FormattingRecord != null
                                              ? FormattingRecord.Clone()
                                              : new CellFormattingRecord();
            return result;
        }

        ///<summary>
        /// Get or set the background fill of the cell
        ///</summary>
        public Fill Background
        {
            get
            {
                Fill result;
                if (FormattingRecord == null)
                    result = null;
                else result = FormattingRecord.Background;
                return result;
            }
            set
            {
                CellFormattingRecord r = FormattingRecord != null
                                             ? FormattingRecord.Clone()
                                             : new CellFormattingRecord();
                r.Background = value;
                SetFormattingRecord(r, CellProperty.Background, "Background");
                Worksheet.OnCellBorderOrBackgroundChanged(this);
            }
        }

        ///<summary>
        /// Get the actual background fill of the cell
        ///</summary>
        public Fill ActualBackground
        {
            get
            {
                DifferentialStyleRecord r = GetDifferentialStyleFor(rr => rr.Background != null);
                if (r != null)
                    return r.Background;
                return Background;
            }
        }

        ///<summary>
        /// Get the actual borders of the cell.
        ///</summary>
        public Borders ActualBorders
        {
            get
            {
                DifferentialStyleRecord r = DifferentialStyleRecord;

                if (r != null && r.Borders != null)
                    return r.Borders;
                if (Borders != null)
                    return Borders;
                if (Row.Borders != null)
                    return Row.Borders;
                return Column.Borders;
            }
        }

        ///<summary>
        /// Get the actual foreground color of the cell.
        ///</summary>
        public SpreadsheetColor ActualForeground
        {
            get
            {
                DifferentialStyleRecord r = GetDifferentialStyleFor(rr => rr.Font != null);
                if (r != null)
                    return r.Font.Foreground;
                return Foreground;
            }
        }

        ///<summary>
        /// Get or set the vertical alignment of the cell.
        ///</summary>
        public VerticalCellAlignment VerticalAlignment
        {
            get
            {
                if (FormattingRecord != null)
                    return FormattingRecord.VerticalAlignment;
                if (TextWrapping == TextWrapping.NoWrap)
                    return VerticalCellAlignment.Bottom;
                return VerticalCellAlignment.Top;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.VerticalAlignment = value;
                SetFormattingRecord(r, CellProperty.VerticalAlignment, "Vertical alignment");
            }
        }

        internal HorizontalCellAlignment ActualHorizontalAlignment
        {
            get
            {
                var result = HorizontalAlignment;
                if (result != HorizontalCellAlignment.General)
                    return result;
                if (Table != null)
                {
                    if (Table.IsHeader(this))
                        return HorizontalCellAlignment.Left;
                }
                return getDefaultAlignment(Value);
            }
        }
        ///<summary>
        /// Get or set the horizontal alignment of the cell.
        ///</summary>
        public HorizontalCellAlignment HorizontalAlignment
        {
            get
            {
                if (FormattingRecord != null)
                {
                    HorizontalCellAlignment? r = FormattingRecord.HorizontalAlignment;
                    if (r != null)
                        return r.Value;
                }
                return HorizontalCellAlignment.General;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.HorizontalAlignment = value;
                SetFormattingRecord(r, CellProperty.HorizontalAlignment, "Horizontal alignment");
                notifyNonRecordPropertyChanged(CellProperties.TextAlignment);
            }
        }

        internal HorizontalCellAlignment getDefaultAlignment(object value)
        {
            if (value == null)
                return HorizontalCellAlignment.Left;
            if (FormattingRecord != null && FormattingRecord.Format != null && FormattingRecord.Format is TextFormat)
                return HorizontalCellAlignment.Left;
            if (value is double)
                return HorizontalCellAlignment.Right;
            if (value is DateTime)
                return HorizontalCellAlignment.Right;
            if (value is bool || value is Error)
                return HorizontalCellAlignment.Center;
            return HorizontalCellAlignment.Left;
        }

        ///<summary>
        /// Get or set the text alignment of the cell
        ///</summary>
        public TextAlignment TextAlignment
        {
            get
            {
                switch (ActualHorizontalAlignment)
                {
                    case HorizontalCellAlignment.Left:
                        return TextAlignment.Left;
                    case HorizontalCellAlignment.Center:
                    case HorizontalCellAlignment.CenterContinuous:
                        return TextAlignment.Center;
                    case HorizontalCellAlignment.Right:
                        return TextAlignment.Right;
                    default:
                        return TextAlignment.Left;
                }
            }
        }

        /* static object OnGetIsActive(DependencyObject d, object baseValue)
         {
             return baseValue;
             Cell c = (Cell)d;
             if (c.Worksheet != null)
                 return c.Worksheet.ActiveCell == c;

             return baseValue;
         }*/
        //\\


        private Row _row;

        ///<summary>
        /// Get the row which contains the current cell.
        ///</summary>
        public Row Row
        {
            get
            {
                Contract.Ensures(Contract.Result<Row>() != null);
                return _row;
            }
            internal set
            {
                _row = value;
                notifyNonRecordPropertyChanged(CellProperties.Row);
            }
        }


        ///<summary>
        /// Get the worksheet which contains the current cell.
        ///</summary>
        public Worksheet Worksheet
        {
            get { return Row.Worksheet; }
        }

        internal void SetAddress(CellAddress a)
        {
            if (Borders == null)
            {
                if (Row.Borders != null)
                    SetCellValue(CellProperty.Borders, Row.Borders);
                else if (Column.Borders != null)
                    SetCellValue(CellProperty.Borders, Column.Borders);
            }
            Row = Worksheet.Rows[a.Row];
            Column = Worksheet.Columns[a.Column];
        }

        public string Label
        {
            get { return Address.Label; }
        }

        internal string FullLabel
        {
            get { return Worksheet.SafeName + "!" + Label; }
        }

        internal CellsRange ToRange()
        {
            Contract.Ensures(Contract.Result<CellsRange>() != null);
            return new SingleCellRange(this);
        }

        private Column _column;

        ///<summary>
        /// Get the column which contains the current cell.
        ///</summary>
        public Column Column
        {
            get { return _column; }
            internal set
            {
                _column = value;
                notifyNonRecordPropertyChanged(CellProperties.Column);
            }
        }


        private void parseRange(string s)
        {
            Contract.Requires(s != null);
            Contract.Ensures(this.Column != null);
            Contract.Ensures(s.StartsWith(Column.Label));
            Column = Worksheet.Columns[CellAddress.PartialParsedColumn(s)];
        }


        private object _value
        {
            get
            {
                return CellData.GetValue
                (
                    _cellData
                )
                ;
            }
            set { _cellData = CellData.SetValue(_cellData, value); }
        }

        public string FormulaFormattedValue
        {
            get
            {
                object v = Value;
                if (Format is PercentFormat && v is double)
                {
                    return new PercentValue((double) v, null).ToString();
                }
                if (Format is NumberFormat && v is double)
                    return v.ToString();
                return FormattedValue;
            }
        }

        public string FormattedValue
        {
            get
            {
                object o = GetRawFormattedValue();
                if (o == null)
                    return null;
                return o.ToString();
            }
        }

        internal object GetRawFormattedValue(int maxCount)
        {
            object val = Value;
            if (val == null)
                return "";
            if (Format == null)
            {
                if (val is DateTime)
                    return getFormatedValue(Core.Formats.General, Value, maxCount);
                return val.ToString();
            }
            return getFormatedValue(Format, Value, maxCount);
        }

        internal object GetRawFormattedValue()
        {
            return GetRawFormattedValue((int) Column.ActualCharacterWidth);
        }

        internal void SetValueAndFormat(object value, bool forceString)
        {
            setExpressionValue(null);
            if (value is string)
            {
                if (forceString)
                {
                    Value = value;
                    return;
                }
                var s = (string) value;
                if (CellFormatter.TrySetBool(this, s)) return;
                if (CellFormatter.TrySetDouble(this, s))
                    return;
                string s2 = s.Trim();
                if (CellFormatter.TrySetPercent(this, s2)) return;
                if (CellFormatter.TrySetCurrency(this, s2))
                    return;

                if (CellFormatter.TrySetTime(this, s2))
                    return;
            }
            CellFormatter.SetDate(this, value);

            //Value = value;
        }


        internal void SetValue(object value)
        {
            object oldValue = Value;
            setRawValue(value);
            onValueChanged(oldValue);
        }

        ///<summary>
        /// Get or set the value of the cell.
        ///</summary>
        ///<exception cref="ArgumentException"></exception>
        public object Value
        {
            get
            {
                object v = _value;
                if (v is VolativeValue)
                    return null;
                if (v == notCalculatedValue)
                {
                    setRawValue(Expression.GetNormalizedValue(new CellEvaluationContext(this)));
                    v = _value;
                }
                Contract.Assert(!(_value is Missing));
                return v;
            }
            set
            {
                if (value is Missing)
                    throw new ArgumentException("value");
                if (value is Range)
                    throw new ArgumentException();
                using (UndoUnitScope undoScope = createTypingUndoScope(value))
                {
                    if (value is RichTextParagraph)
                    {
                        var p = (RichTextParagraph) value;
                        p.Cell = this;
                        Value = p.Text;
                        EnsureExtraData.Paragraph = p;
                        return;
                    }
                    if (value is Hyperlink)
                    {
                        Hyperlink = (Hyperlink) value;
                        Value = ((Hyperlink) value).Display;
                        return;
                    }
                    if (HasExtraData)
                    {
                        ExtraData.Paragraph = null;
                    }
                    if (_expression != null)
                        Expression = null;
                    else
                        onPreviewValueChanged();
                    bool hasOldValue = !NeedsCalculate;
                    object oldValue = !NeedsCalculate ? Value : null;
                    promote();
                    value = CellHelper.NormalizedValue(value);
                    if (Format != null)
                    {
                        if (Format is DateTimeFormat && value is Double)
                        {
                            var dbl = (double) value;
                            if (dbl < DateTimeConverter.maxDouble)
                                value = Workbook.ChangeType<DateTime>((double) value);
                        }
                    }
                    setRawValue(value);
                    notifyRecordPropertyChanged(CellProperty.HorizontalAlignment);
                    onValueChanged(oldValue);
                    notifyRecordPropertyChanged(CellProperty.Foreground);
                    //\\ Workbook.DependencyManager.Changed(this);
                    if (Workbook.CalculationProperties.CalculationMode != CalculationMode.Manual)
                        Recalculate();
                    //\\SetAutoNumberFormat();
                    setDirty();
                    if (HasExtraData)
                        EnsureExtraData.OnCellChanged(this);
                    if (UndoEnabled && hasOldValue)
                    {
                        using (createTypingUndoScope(value))
                        {
                            Workbook.AddUndoUnit(new SetCellValueUndoUnit(this, oldValue));
                        }
                    }
                }
            }
        }

        private UndoUnitScope   createTypingUndoScope(object value)
        {
            if (!UndoEnabled)
                return null;
            object toFormat = value;
            if (toFormat is string)
            {
                var s = (string) toFormat;
                if (s.Length > 20)
                    s = s.Substring(0, 20) + "...";
            }
            return Worksheet.CreateUndoScope((string.Format(StringTable.Typing, toFormat, Label)));
        }

        /// <summary>
        /// Gets the rich value
        /// </summary>
        public RichTextParagraph RichValue
        {
            get
            {
                if (!HasExtraData)
                    return null;
                return ExtraData.Paragraph;
            }
            set
            {
                Contract.Ensures(RichValue == value);
                Value = value;
            }
        }

        private void onValueChanged(object oldValue)
        {
            Contract.Requires(!(oldValue is UncalculatedValue));
            if (this.Worksheet.CellValueChangedDisabled)
                return;
            notifyNonRecordPropertyChanged(CellProperties.CanOverflow);
            notifyValueChanged();
            if (HasExtraData)
                EnsureExtraData.OnCellChanged(this);
            Worksheet.NotifyValueChanged(this, oldValue);
        }
        private void onPreviewValueChanged ()
        {
            Worksheet.NotifyPreviewValueChanged(this);
        }

        internal void Recalculate()
        {
            Workbook.CalculateCell(this);
        }

        private void setDirty()
        {
            Workbook.Document.SetDirty();
        }

        internal string FormulaWithoutEqual
        {
            get
            {
                if (HasExtraData && ExtraData.FormulaWithoutEqual != null)
                    return ExtraData.FormulaWithoutEqual;
                if (Expression != null)
                    return Expression.GetText(new TextContext(new CellEvaluationContext(this)));
                return null;
            }
        }

        ///<summary>
        /// Get or set the formula of the cell. Changing the cell may also change the value.
        ///</summary>
        public string Formula
        {
            get
            {
                try
                {
                    if (HasExtraData && ExtraData.FormulaWithoutEqual != null)
                        return "=" + ExtraData.FormulaWithoutEqual;
                    if (Expression != null)
                        return Expression.GetTextWithEqual(new TextContext(new CellEvaluationContext(this)));
                    return null;
                }
                catch (Exception e)
                {
                    Debug.WriteLine("Invalid formula " + e);
                    return "Invalid formula " + e.Message;
                }
            }
            set
            {
                SetFormulaImp(value, LocalizationMode.NonLocalized, null);
            }
        }

        internal void SetFormulaImp(string value, LocalizationMode localized, Range arrayFormula)
        {
            using (createTypingUndoScope(value))
            {
                setDirty();
                if (string.IsNullOrEmpty(value))
                {
                    if (Workbook.CalculationProperties.CalculationMode != CalculationMode.Manual)
                        Value = null;
                    Expression = null;
                    if (HasExtraData && ExtraData.FormulaWithoutEqual == null)
                    {
                        ExtraData.FormulaWithoutEqual = null;
                        ForgetExtraData();
                    }
                    notifyFormulaChanged();
                    return;
                }
                promote();
                if (Workbook.CalculationProperties.CalculationMode == CalculationMode.Manual)
                {
                    if (value.StartsWith("="))
                    {
                        value = value.Substring(1);
                        EnsureExtraData.FormulaWithoutEqual = value;
                        return;
                    }
                }
                var p = new ExpressionParser(Worksheet, this, localized,
                                             arrayFormula != null ? CellReferenceMode.Static : CellReferenceMode.Dynamic);
                Expression exp = p.Expression(value);
                if (p.Error != null)
                {
                    Debug.WriteLine("Unable to parse " + value);
                    return;
                }
                if (arrayFormula != null && exp != null)
                    exp = new ArrayFormulaReference(new ArrayFormula(exp, arrayFormula));
                if (exp is StringConstantExpression && value.StartsWith("="))
                {
                    ((StringConstantExpression) exp).ForceString = true;
                }
                SetExpression(exp);
            }
        }

        ///<summary>
        /// Set or get the formula in the current locale.
        ///</summary>
        public string LocalizedFormula
        {
            get
            {
                if (HasExtraData && ExtraData.FormulaWithoutEqual != null)
                    return "=" + ExtraData.FormulaWithoutEqual;
                if (Expression != null)
                    return Expression.GetTextWithEqual(new TextContext(new CellEvaluationContext(this), Workbook.Document.Context));
                return null;
            }
            set { SetFormulaImp(value, LocalizationMode.Localized, null); }
        }

        /// <summary>
        /// Gets the range of the array formula. Returns null if the the formula is not an array formula
        /// </summary>
        [Obsolete("Use ArrayFormulaRange instead")]
        public Range FormulaArray
        {
            get
            {
                var exp = Expression as ArrayFormulaReference;
                if (exp == null)
                    return null;
                return exp.ArrayFormula.Range;
            }
        }
        /// <summary>
        /// Gets the range of the array formula. Returns null if the the formula is not an array formula
        /// </summary>
        public Range ArrayFormulaRange
        {
            get
            {
                var exp = Expression as ArrayFormulaReference;
                if (exp == null)
                    return null;
                return exp.ArrayFormula.Range;
            }
        }
        internal void SetExpression(Expression exp)
        {
            Expression = exp;
            //\\if (Workbook.TempReader == null)
            {
                InvalidateExpression();
                //\\Recalculate();
            }
            notifyFormulaChanged();
            notifyValueChanged();
            notifyNonRecordPropertyChanged(CellProperties.HorizontalAlignment);
            notifyNonRecordPropertyChanged(CellProperties.CanOverflow);
        }

        private void notifyValueChanged()
        {
            if (this.Worksheet.CellValueChangedDisabled)
                return;
            notifyNonRecordPropertyChanged(CellProperties.Value);
            notifyFormattedValueChanged();
        }

        private void notifyRecordPropertyChanged(CellProperty name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name.ToString()));
        }

        private void notifyNonRecordPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            switch (name)
            {
                case CellProperties.Background:
                case CellProperties.Borders:
                case CellProperties.ActualBackground:
                case CellProperties.ActualBorders:
                    this.Worksheet.OnCellBorderOrBackgroundChanged(this);
                    break;
            }
        }

        private Workbook Workbook
        {
            get { return Worksheet.Workbook; }
        }

        ///<summary>
        /// Set or get the format of the cell. If the value is not set, the General format will be used to display the content of the cell.
        ///</summary>
        public Format Format
        {
            get
            {
                if (FormattingRecord != null)
                {
                    if (FormattingRecord.Format == null)
                        return Core.Formats.General;
                    return FormattingRecord.Format;
                }
                return Core.Formats.General;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.Format = value;
                SetFormattingRecord(r, CellProperty.Format, "Format");
                notifyRecordPropertyChanged(CellProperty.HorizontalAlignment);
                notifyFormattedValueChanged();
            }
        }

        private void notifyFormattedValueChanged()
        {
            notifyNonRecordPropertyChanged(FormattedValueName);
            notifyNonRecordPropertyChanged(CellProperties.FormulaFormattedValue);
        }


        internal void SetFormattingRecordSafe(CellFormattingRecord r)
        {
            if (r == _formattingRecord)
                return;
            setDirty();
            EnsurePromoted();
            SetFormattingRecord(r, CellProperty.Background); //\\any property will do
            Worksheet.OnCellBorderOrBackgroundChanged(this);
            notifyFormattingRecordPropertyChanged();
        }

        internal void SetFormattingRecordUnsafe(CellFormattingRecord value)
        {
            Contract.Assert(value == null || Workbook.StyleSheet.ContainsRecord(value));
            setDirty();
            EnsurePromoted();

            if (UndoEnabled)
            {
                Workbook.AddUndoUnit(new CellFormattingRecordUndoUnit(this, FormattingRecord));
            }

            FormattingRecord = value;
            notifyFormattingRecordPropertyChanged();
        }

        private void notifyFormattingRecordPropertyChanged()
        {
            if (this.Worksheet.CellPropertyEventDisabled)
                return;
            notifyRecordPropertyChanged(CellProperty.Format);
            notifyRecordPropertyChanged(CellProperty.Foreground);
            notifyRecordPropertyChanged(CellProperty.VerticalAlignment);
            notifyRecordPropertyChanged(CellProperty.HorizontalAlignment);
            notifyRecordPropertyChanged(CellProperty.TextWrapping);
            notifyRecordPropertyChanged(CellProperty.Borders);
            notifyRecordPropertyChanged(CellProperty.Background);
            notifyRecordPropertyChanged(CellProperty.FontStyle);
            notifyRecordPropertyChanged(CellProperty.FontUnderline);
            notifyRecordPropertyChanged(CellProperty.FontSize);
            notifyRecordPropertyChanged(CellProperty.FontFamily);
            notifyRecordPropertyChanged(CellProperty.FontWeight);
            NotifyDifferentialStyleRecordChanged();
        }

        private CellFormattingRecord _formattingRecord
        {
            get { return CellData.GetFormattingRecord(_cellData);
            }
            set { _cellData = CellData.SetFormattingRecord(_cellData, value); }
        }

        internal CellFormattingRecord FormattingRecord
        {
            get { return _formattingRecord; }
            private set { _formattingRecord = value; }
        }

        private bool hasDefaultFormattingRecord()
        {
            if (FormattingRecord == null)
                return true;
            if (FormattingRecord == Workbook.StyleSheet.DefaultStyleRecord)
                return true;
            return false;
        }

        private void promote()
        {
            if (State == CellState.Promoted)
                return;
            setRawValue(null);
            Worksheet.Promote(this);
        }


        private const string xmlName = "c";

        ///<summary>
        /// Get or set the the borders of the cell.
        ///</summary>
        public Borders Borders
        {
            get
            {
                if (FormattingRecord == null)
                    return null;
                return FormattingRecord.Borders;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                r.Borders = value;
                SetFormattingRecord(r, CellProperty.Borders, StringTable.Borders);
                Worksheet.OnCellBorderOrBackgroundChanged(this);
            }
        }

        public bool HasValueExpressionOrHyperlink
        {
            get
            {
                if (Value != null)
                    return true;
                if (_expression != null)
                    return true;
                if (Hyperlink != null)
                    return true;
                return false;
                
            }

        }
        internal bool IsEmpty
        {
            get
            {
                if (Value != null)
                    return false;
                if (_expression != null)
                    return false;
                if (FormattingRecord != null)
                    return false;
                if (Hyperlink != null)
                    return false;
                return true;
            }
        }

        internal bool HasEmptyText
        {
            get
            {
                object v = Value;
                if (v == null)
                    return true;
                if (v is string)
                {
                    var s = (string) v;
                    return s.Length == 0;
                }
                return false;
            }
        }

        private CellType getCellType()
        {
            object value = Value;
            if (value is string)
            {
                return Formula == null ? CellType.SharedString : CellType.FormulaString;
            }
            if (value is Error)
                return CellType.Error;
            if (value is double || value is DateTime || value is float || value is int || value is float ||
                value is decimal || value is long || value is ulong || value is byte || value is sbyte || value is uint)
                //\\needed?
                return CellType.Number;
            if (value is bool)
                return CellType.Boolean;
            if (value is Complex)
                return CellType.FormulaString;
            if (value is RichTextParagraph)
                return CellType.SharedString;
            return CellType.Error;
        }


        internal void CopyFormattingRecord(Cell source)
        {
            Contract.Requires(source.Worksheet.Workbook == Worksheet.Workbook);
            SetFormattingRecordUnsafe(source.FormattingRecord);
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal RangeAddress GetRangeAddress()
        {
            return new RangeAddress(Address, Address + new CellRelativeAddress(RowSpan - 1, ColumnSpan - 1));
        }

        internal object GetCellValue(CellProperty p)
        {
            switch (p)
            {
                case CellProperty.Background:
                    return Background;
                case CellProperty.Foreground:
                    return Foreground;
                case CellProperty.VerticalAlignment:
                    return VerticalAlignment;
                case CellProperty.HorizontalAlignment:
                    return HorizontalAlignment;
                case CellProperty.FontWeight:
                    return FontWeight;
                case CellProperty.FontStyle:
                    return FontStyle;
                case CellProperty.FontUnderline:
                    return FontUnderline;
                case CellProperty.TextWrapping:
                    return TextWrapping;
                case CellProperty.FontSize:
                    return FontSize;
                case CellProperty.FontFamily:
                    return FontFamily;
                case CellProperty.Indentation:
                    return Indentation;
                case CellProperty.Format:
                    return Format;
                case CellProperty.IsLocked:
                    return IsLocked;
                case CellProperty.Borders:
                    return Borders;
                case CellProperty.IsVertical:
                    return IsVertical;
                case CellProperty.Rotation:
                    return Rotation;
                case CellProperty.IsHidden:
                    return IsFormulaHidden;
                default:
                    throw new NotSupportedException(p.ToString());
            }
        }

        internal void SetCellValue(CellProperty p, object value)
        {
            switch (p)
            {
                case CellProperty.Background:
                    Background = (Fill) value;
                    break;
                case CellProperty.Foreground:
                    Foreground = (SpreadsheetColor) value;
                    break;
                case CellProperty.VerticalAlignment:
                    VerticalAlignment = (VerticalCellAlignment) value;
                    break;
                case CellProperty.HorizontalAlignment:
                    HorizontalAlignment = (HorizontalCellAlignment) value;
                    break;
                case CellProperty.FontWeight:
                    FontWeight = (FontWeight) value;
                    break;
                case CellProperty.FontStyle:
                    FontStyle = (FontStyle) value;
                    break;
                case CellProperty.FontUnderline:
                    FontUnderline = (UnderlineType) value;
                    break;
                case CellProperty.TextWrapping:
                    TextWrapping = (TextWrapping) value;
                    break;
                case CellProperty.FontSize:
                    FontSize = (double) value;
                    break;
                case CellProperty.FontFamily:
                    if (value is string)
                        value = new FontFamily((string) value);
                    FontFamily = (FontFamily) value;
                    break;
                case CellProperty.Indentation:
                    Indentation = (double) value;
                    break;

                case CellProperty.Format:
                    Format = (Format) value;
                    break;
                case CellProperty.Borders:
                    Borders = (Borders) value;
                    break;
                case CellProperty.IsLocked:
                    IsLocked = (bool) value;
                    break;
                case CellProperty.IsVertical:
                    IsVertical = (bool) value;
                    break;
                case CellProperty.Rotation:
                    Rotation = (int) value;
                    break;
                case CellProperty.IsHidden:
                    IsFormulaHidden = (bool) value;
                    break;
                default:
                    throw new NotSupportedException();
            }
        }


        internal void ClearCellValue(CellProperty p)
        {
            if (FormattingRecord == null)
                return;
            CellFormattingRecord r = newFormattingRecord();
            r.ClearValue(p);
            SetFormattingRecord(r, p);
        }


        internal void ClearContent()
        {
            Value = null;
            this.setExpressionValue(null);
        }

        internal void InvalidateFontFamily()
        {
            notifyRecordPropertyChanged(CellProperty.FontFamily);
        }

        internal void Delete()
        {
            Value = Error.CreateRefError(Value);
        }

        internal void Select()
        {
            Worksheet.ActiveCell = this;
            Worksheet.Selection = ToRange();
        }

        internal void SetNonVolatile()
        {
            Contract.Requires(State == CellState.Volatile);
            setRawValue(null);
        }

        internal void Undelete()
        {
            var e = _value as RefError;
            if (e == null)
                return;
            setRawValue(e.value);
            if (Workbook.CalculationProperties.CalculationMode != CalculationMode.Manual)
                Recalculate();
            //\\Workbook.DependencyManager.Changed(this);
        }

        internal void ClearValue()
        {
            using (Worksheet.CreateUndoScope(StringTable.Clear))
            {
                Value = null;
                Expression = null;
            }
        }

        internal Cell GetNeighbor(NavigationDirection direction, int length)
        {
            switch (direction)
            {
                case NavigationDirection.Up:
                    return Worksheet[Math.Max(0, Row.Index - length), Column.Index];
                case NavigationDirection.Down:
                    return Worksheet[Math.Max(0, Row.Index + length), Column.Index];
                case NavigationDirection.Left:
                    return Worksheet[Row.Index, Math.Max(0, Column.Index - length)];
                case NavigationDirection.Right:
                    return Worksheet[Row.Index, Math.Max(0, Column.Index + length)];
            }
            throw new NotSupportedException();
        }

        /// <summary>
        /// Get or set the hyperlink of the current cell
        /// </summary>
        public Hyperlink Hyperlink
        {
            get
            {
                if (!HasExtraData)
                    return null;
                return ExtraData.hl;
            }
            set
            {
                using (Workbook.CreateUndoScope(StringTable.Hyperlink))
                {
                    Workbook.AddUndoAction(Hyperlink, h => Hyperlink = h);
                    EnsureExtraData.hl = value;
                    if (value != null)
                    {
                        value.Source = this;
                        promote();
                    }

                    ForgetExtraData();
                    promote();
                    notifyNonRecordPropertyChanged(CellProperties.Hyperlink);
                }
            }
        }

        internal TableCell ToTableCell()
        {
            SpreadsheetColor backgroundColor = null;
            PatternFill background = (PatternFill)ActualBackground;
            if (background != null)
            {
                backgroundColor = background.BackgroundColor;
                if (backgroundColor == null)
                    backgroundColor = background.ForegroundColor;
            }
            TableCell result = new TableCell();
            if (background != null)
                result.Background = ColorBrushManager.GetBrush(backgroundColor, Worksheet.Workbook.ColorScheme);
            if (this.Borders != null)
            {
                Thickness thickness = new Thickness(Borders.GetBorderThickness(BorderType.LeftEdge),
                    Borders.GetBorderThickness(BorderType.TopEdge),
                    Borders.GetBorderThickness(BorderType.RightEdge),
                    Borders.GetBorderThickness(BorderType.BottomEdge));
                result.BorderThickness = thickness;
                if (Borders.Left != null)
                    result.BorderBrush = new SolidColorBrush(Borders.Left.GetWPFColor(this.Workbook.ColorScheme));
                if (Borders.Top != null)
                    result.BorderBrush = new SolidColorBrush(Borders.Top.GetWPFColor(this.Workbook.ColorScheme));
                if (Borders.Right != null)
                    result.BorderBrush = new SolidColorBrush(Borders.Right.GetWPFColor(this.Workbook.ColorScheme));
                if (Borders.Bottom != null)
                    result.BorderBrush = new SolidColorBrush(Borders.Bottom.GetWPFColor(this.Workbook.ColorScheme));
            }
            var paragraph = new Paragraph(ToRun());
            result.Blocks.Add(paragraph);
            return result;
        }

        private Run ToRun()
        {
            var r = new Run(FormattedValue)
                        {
                            FontSize = (FontSize*4/3),
                            FontWeight = FontWeight,
                            FontFamily = FontFamily,
                            FontStyle = FontStyle,
                            Foreground = ColorBrushManager.GetBrush(ActualForeground, Workbook.ColorScheme),
                        };
            switch (ActualFontUnderline)
            {
                case UnderlineType.None:
                    break;
                case UnderlineType.Single:
                    break;
                case UnderlineType.Double:
                    break;
                case UnderlineType.SingleAccounting:
                    break;
                case UnderlineType.DoubleAccounting:
                    break;
                default:
                    break;
            }
            return r;
        }

        internal Typeface Typeface
        {
            get
            {
                FontFamily ff = this.FontFamily;
                return SpreadsheetFont.CreateTypeface(ff, ActualFontStyle, ActualFontWeight);
            }
        }

        /// <summary>
        /// Gets the angle of the rotation for the text
        /// </summary>
        public int Rotation
        {
            get
            {
                if (FormattingRecord == null)
                    return 0;
                if (IsVertical)
                    return 0;
                int r = FormattingRecord.TextRotation;
                if (r <= 90)
                    return r;
                return 90 - r;
            }
            set
            {
                CellFormattingRecord r = newFormattingRecord();
                if (value > 0)
                    r.TextRotation = value;
                else
                    r.TextRotation = 90 - value;
                SetFormattingRecord(r, CellProperty.Rotation, StringTable.Rotation);
            }
        }


        internal static Cell CreateCellWithRow(Row row)
        {
            return new Cell(row);
        }

        internal void SetFormula(string value, bool recalc)
        {
            setDirty();
            if (string.IsNullOrEmpty(value))
            {
                if (Workbook.CalculationProperties.CalculationMode != CalculationMode.Manual)
                    Value = null;
                Expression = null;
                if (HasExtraData && ExtraData.FormulaWithoutEqual == null)
                {
                    ExtraData.FormulaWithoutEqual = null;
                    ForgetExtraData();
                }
                notifyFormulaChanged();

                return;
            }
            promote();
            if (Workbook.CalculationProperties.CalculationMode == CalculationMode.Manual)
            {
                if (value.StartsWith("="))
                {
                    value = value.Substring(1);
                    EnsureExtraData.FormulaWithoutEqual = value;
                    return;
                }
            }
            var p = new ExpressionParser(Worksheet, this, LocalizationMode.NonLocalized, CellReferenceMode.Dynamic);
            Expression exp = p.Expression(value);
            if (p.Error != null)
            {
                Debug.WriteLine("Unable to parse " + value);
                return;
            }
            SetExpressionRecalc(exp, false);
        }

        /// <summary>
        /// Gets the conditional formatting which contains the cell, if any
        /// </summary>
        internal List<ConditionalFormatting> ConditionalFormattings
        {
            get
            {
                if (!HasExtraData)
                    return null;
                return EnsureExtraData.ConditionalFormatting;
            }
            set
            {
                if (value == ConditionalFormattings)
                    return;
                using (this.Worksheet.Workbook.CreateUndoScope(StringTable.ConditionalFormatting))
                {
                    this.Worksheet.Workbook.AddUndoAction(ConditionalFormattings, c => this.ConditionalFormattings = value);
                    if (value != null)
                        EnsureExtraData.ConditionalFormatting = value;
                    else if (HasExtraData)
                    {
                        EnsureExtraData.ConditionalFormatting = value;
                        ForgetExtraData();
                    }
                    Worksheet.OnCellBorderOrBackgroundChanged(this);
                }
            }
        }

        internal DateTime? DateValue
        {
            get
            {
                object v = Value;
                if (v is DateTime)
                    return (DateTime) v;
                if (v is double)
                    return Workbook.DoubleToDateTime((double) v);
                return null;
            }
        }

        internal bool IsSingle
        {
            get
            {
                if (!HasExtraData)
                    return true;
                return ColumnSpan * RowSpan == 1;
                
            }
        }

        internal void SetBorder(Border border, BorderType borderType)
        {
            Borders borders = null;
            if ((borderType & BorderType.LeftEdge) != 0)
                borders = TransformBorders(borders, b => b.Left = border);
            if ((borderType & BorderType.TopEdge) != 0)
                borders = TransformBorders(borders, b => b.Top = border);
            if ((borderType & BorderType.RightEdge) != 0)
                borders = TransformBorders(borders, b => b.Right = border);
            if ((borderType & BorderType.BottomEdge) != 0)
                borders = TransformBorders(borders, b => b.Bottom = border);

            if (borders != Borders)
                Borders = borders;
        }

        private Borders TransformBorders(Borders borders, Action<Borders> action)
        {
            Borders result;
            if (borders != null)
                result = borders;
            else
            {
                if (Borders == null)
                    result = new Borders();
                else
                    result = Borders.Clone();
            }
            action(result);
            return result;
        }


        internal Border GetActualBorder(BorderType borderType)
        {
            Borders actualBorders = ActualBorders;
            if (actualBorders == null)
                return null;
            return actualBorders.GetBorder(borderType);
        }

    }
}